#! /usr/bin/env python
#coding=utf-8

import os
import sys
import json
import sqlite3
import pprint

sys.path.append('../')
from startup_config.config_builder import FileTableBuilder
from startup_config.config_builder import CmdTableBuilder
from startup_config.config_builder import JobTableBuilder
from startup_config.config_builder import ServiceTableBuilder
from startup_config.config_builder import ServiceSocketTableBuilder
from startup_config.config_builder import ServiceFileTableBuilder

def _create_arg_parser():
    import argparse
    parser = argparse.ArgumentParser(description='Collect init config information from xxxx/etc/init dir.')
    parser.add_argument('-i', '--input',
                        help='input init config files base directory example "out/rk3568/packages/phone/" ',
                        action='append', required=True)

    parser.add_argument('-o', '--output',
                        help='output init config information database directory', required=False)
    parser.add_argument('-b', '--bootevent',
                        help='input bootevent file from system ', required=False)
    return parser

class ItemParser(dict):
    def __init__(self, config):
        self._config_parser = config
        self["name"] = ""
    def create(self, jsonNode, parent=None, fileId = None):
        return

    def update(self, jsonNode, parent=None, fileId = None):
        return

    def getName(self):
        return self["name"]

    def get(self, key):
        if self.__contains__(key):
            return self[key]
        return None

    # get value form json array
    def getStringsValue(self, jsonStrArray):
        if jsonStrArray == None or len(jsonStrArray) == 0:
            return ""

        string = jsonStrArray[0]
        for i in range(1, len(jsonStrArray)):
            string = string + "@" + jsonStrArray[i]
        return string

class CmdParser(ItemParser):
    def __init__(self, config):
        ItemParser.__init__(self, config)
        self["content"] = ""
        self["fileId"] = -1

    def create(self, jsonNode, parent=None, fileId = None):
        assert(isinstance(jsonNode, str))
        assert(parent != None)
        info = jsonNode.partition(" ") # 取第一个空格分割
        self["name"] = info[0]
        self["jobId"] = parent.get("jobId")
        if fileId:
            self["fileId"] = fileId
        if len(info) > 2:
            self["content"] = info[2]
        #print("Create cmd %s %d" % (self["name"], self["fileId"]))
        return

    def __str__(self):
        return "cmd \"%s\"  content \"%s\" " % (self["name"], self["content"])

class JobParser(ItemParser):
    def __init__(self, config):
        ItemParser.__init__(self, config)
        self["condition"] = ""
        self["serviceId"] = -1
        self["fileId"] = -1
        self["jobPriority"] = -1
        self["jobPriority"] = -1
        self["executionTime"] = 0

    def _addCmds(self, cmdList, fileId):
        for cmd in cmdList:
            self._config_parser.addCmd(cmd, self, fileId)

    def create(self, jsonNode, parent=None, fileId = None):
        assert(isinstance(jsonNode, dict))
        self["name"] = jsonNode["name"]
        self["jobId"] = self._config_parser.getJobId()
        #print("JobParser %s %d" % (jsonNode["name"], fileId))
        self["jobPriority"] = self._config_parser.getJobPriority(jsonNode["name"])

        if fileId and self["fileId"] is None:
            self["fileId"] = fileId
        if parent != None:
            self["serviceId"] = parent.get("serviceId")

        if jsonNode.__contains__("condition"):
            self["condition"] = jsonNode.get("condition")
        if jsonNode.__contains__("cmds"):
            self._addCmds(jsonNode.get("cmds"), fileId)

        return

    def update(self, jsonNode, parent=None, fileId = None):
        assert(isinstance(jsonNode, dict))
        if parent != None:
            self["serviceId"] = parent.get("serviceId")
        if fileId and self["fileId"] is None:
            self["fileId"] = fileId
        if jsonNode.__contains__("cmds"):
            self._addCmds(jsonNode.get("cmds"), fileId)
        return

    def __str__(self):
        return "jobs '%s'  condition '%s' " % (self["name"], self["condition"])

class ServiceParser(ItemParser):
    def __init__(self, config):
        ItemParser.__init__(self, config)
        self["critical_enable"] = False
        self["limit_time"] = 20
        self["limit_count"] = 4
        self["importance"] = 0
        self["once"] = False
        self["console"] = False
        self["notify_state"] = True
        self["on_demand"] = False
        self["sandbox"] = False
        self["disabled"] = False
        self["start_mode"] = "normal"
        self["secon"] = ""
        self["boot_job"] = ""
        self["start_job"] = ""
        self["stop_job"] = ""
        self["restart_job"] = ""
        self["path"] = ""
        self["apl"] = ""
        self["d_caps"] = ""
        self["permission"] = ""
        self["permission_acls"] = ""
        self["fileId"] = -1

    def _handleStringFiled(self, jsonNode):
        strFieldMap = {
            "uid" : "uid", "caps":"caps", "start_mode":"start-mode", "secon":"secon", "apl":"apl"
        }
        for key, name in strFieldMap.items():
            if jsonNode.__contains__(name):
                self[key] = jsonNode.get(name)

    def _handleIntegerFiled(self, jsonNode):
        strFieldMap = {
            "importance" : "importance"
        }
        for key, name in strFieldMap.items():
            if jsonNode.__contains__(name):
                self[key] = jsonNode.get(name)

    def _handleBoolFiled(self, jsonNode):
        boolFieldMap = {
            "once" : "once", "console" : "console", "notify_state" : "notify_state",
            "on_demand" : "ondemand", "sandbox" : "sandbox", "disabled" : "disabled",
            "critical_enable" : "critical_enable"
        }
        for key, name in boolFieldMap.items():
            if jsonNode.__contains__(name):
                value = jsonNode.get(name)
                if isinstance(value, bool):
                    self[key] = value
                elif isinstance(value, int):
                    self[key] = value != 0

    def _handleArrayFiled(self, jsonNode):
        arrayFieldMap = {
            "path" : "path", "gid" : "gid", "cpu_core" : "cpucore", "caps":"caps", "write_pid":"writepid",
            "d_caps":"d-caps", "permission":"permission", "permission_acls":"permission_acls",
        }
        for key, name in arrayFieldMap.items():
            if jsonNode.__contains__(name) :
                self[key] = self.getStringsValue(jsonNode.get(name))

    def _handleScopeJobs(self, jsonNode):
        jobFieldMap = {
            "boot_job" : "on_boot", "start_job" : "on-start", "stop_job":"on-stop", "restart_job":"on-restart"
        }
        for key, name in jobFieldMap.items():
            if jsonNode.__contains__(name):
                self[key] = jsonNode.get(name)
                self._config_parser.addJob({"name" : jsonNode.get(name)}, self, self["fileId"])

    def create(self, jsonNode, parent=None, fileId = None):
        assert(isinstance(jsonNode, dict))
        self["name"] = jsonNode["name"]
        if not self.get("serviceId") :
            self["serviceId"] = self._config_parser.getServiceId()
        if fileId :
            self["fileId"] = fileId
        self._handleStringFiled(jsonNode)
        self._handleBoolFiled(jsonNode)
        self._handleArrayFiled(jsonNode)
        self._handleIntegerFiled(jsonNode)

        #for file
        if jsonNode.__contains__("file"):
            for item in jsonNode.get("file"):
                self._config_parser.addServiceFile(item, self)

        #for socket
        if jsonNode.__contains__("socket"):
            for item in jsonNode.get("socket"):
                self._config_parser.addServiceSocket(item, self)
        #for jobs
        if jsonNode.__contains__("jobs"):
            self._handleScopeJobs(jsonNode.get("jobs"))

        #for critical
        if jsonNode.__contains__("critical"):
            critical = jsonNode.get("critical")
            if isinstance(critical, list):
                self["critical_enable"] = int(critical[0]) != 0
                self["limit_time"] = int(critical[0])
                self["limit_count"] = int(critical[0])
            else:
                self["critical_enable"] = int(critical) != 0
        return

    def update(self, jsonNode, parent=None, fileId = None):
        self.create(jsonNode, parent, fileId)
        return

class ServiceSocketParser(ItemParser):
    def __init__(self, config):
        ItemParser.__init__(self, config)
        self["family"] = ""
        self["type"] = ""
        self["protocol"] = ""
        self["permissions"] = ""
        self["uid"] = ""
        self["gid"] = ""
        self["serviceId"] = -1

    def create(self, jsonNode, parent=None, fileId = None):
        assert(isinstance(jsonNode, dict))
        self["name"] = jsonNode["name"]
        if parent != None:
            self["serviceId"] = parent.get("serviceId")
        fields = ["family", "type", "protocol", "permissions", "uid", "gid"]
        for field in fields:
            if jsonNode.get(field) :
                self[field] = jsonNode.get(field)
        if jsonNode.get("option") :
            self["option"] = self.getStringsValue(jsonNode.get("option"))

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return "socket '%s' serviceid = %d family %s" % (self["name"], self["serviceId"], self["family"])

class ServiceFileParser(ItemParser):
    def __init__(self, config):
        ItemParser.__init__(self, config)
        self["name"] = ""
        self["content"] = ""
        self["serviceId"] = -1

    def create(self, jsonNode, parent=None, fileId = None):
        assert(isinstance(jsonNode, str))
        if parent != None:
            self["serviceId"] = parent.get("serviceId")
        info = jsonNode.partition(" ")
        self["name"] = info[0]
        if len(info) > 2:
            self["content"] = info[2]
        return

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return "file '%s' serviceid = %d content '%s'" % (self["name"], self["serviceId"], self["content"])

class ConfigParser():
    def __init__(self, path):
        self._path = path
        self._jobs = {}
        self._files = {}
        self._cmds = []
        self._services = {}
        self._serviceSockets = {}
        self._serviceFiles = {}
        self._jobId = 0
        self._fileId = 0
        self._serviceId = 0

    def _loadServices(self, jsonNode, fileId):
        assert(isinstance(jsonNode, list))
        for item in jsonNode:
            self.addService(item, fileId)
        return

    def _loadJobs(self, jsonNode, fileId):
        assert(isinstance(jsonNode, list))
        for item in jsonNode:
            self.addJob(item, None, fileId)
        return

    def _loadImport(self, importNode):
        assert(isinstance(importNode, list))
        startWith = [ "/system", "/chip_prod", "/sys_prod", "/vendor" ]
        for file in importNode:
            found = False
            for start in startWith:
                if file.startswith(start):
                    found = True
                    break
            if found :
                self.loadConfig(self._path + file)
            else:
                for start in startWith:
                    self.loadConfig(self._path + start + file, file)

    def loadConfig(self, fileName):
        path = self._path + fileName
        if not os.path.exists(path):
            print("Error, invalid config file %s" % path)
            return
        with open(path, encoding='utf-8') as content:
            try:
                root = json.load(content)
                fileId = self.addFile(fileName)
                print("loadConfig %d fileName = %s" % (fileId, fileName))
                assert(isinstance(root, dict))
                if (root.__contains__("services")):
                    self._loadServices(root["services"], fileId)
                if (root.__contains__("jobs")):
                    self._loadJobs(root["jobs"], fileId)
                if (root.__contains__("import")):
                    self._loadImport(root["import"])
                    pass
            except:
                pass

    def addFile(self, fileName, subsystemName = "startup", partName = "init"):
        if self._files.get(fileName):
            return self._files.get(fileName).get("fileId")
        self._fileId = self._fileId + 1
        self._files[fileName] = {
            "fileId" : self._fileId,
            "fileName" : fileName,
            "subsystemName" : subsystemName,
            "partName" : partName
        }
        return self._files[fileName].get("fileId")

    def getFiles(self):
        return self._files

    def addJob(self, item, service, fileId):
        if self._jobs.get(item.get("name")):
            self._jobs.get(item.get("name")).update(item, service, fileId)
            return
        parser = JobParser(self)
        parser.create(item, service, fileId)
        self._jobs[parser.getName()] = parser

    def addCmd(self, item, job, fileId):
        parser = CmdParser(self)
        parser.create(item, job, fileId)
        self._cmds.append(parser)

    def addService(self, item, fileId):
        if self._services.get(item.get("name")):
            self._services.get(item.get("name")).update(item)
            return
        parser = ServiceParser(self)
        parser.create(item, None, fileId)
        self._services[parser.get("name")] = parser

    def addServiceSocket(self, item, service):
        parser = ServiceSocketParser(self)
        parser.create(item, service)
        self._serviceSockets[parser.getName()] = parser

    def addServiceFile(self, item, service):
        parser = ServiceFileParser(self)
        parser.create(item, service)
        self._serviceFiles[parser.getName()] = parser

    def getJobId(self):
        self._jobId = self._jobId + 1
        return self._jobId

    def getServiceId(self):
        self._serviceId = self._serviceId + 1
        return self._serviceId

    def dumpConfig(self):
        print("Dump jobs: \n")
        pp = pprint.PrettyPrinter(indent = 0, compact=True)
        pp.pprint(self._jobs)
        pass

    def _saveConfig(self, objs, builder):
        cnt = 0
        script = "BEGIN TRANSACTION;\n"
        for item in objs:
            sqlcmd = builder.addObjectToDb(item, False, True)
            #print(sqlcmd)
            script = script + sqlcmd + ";\n"
            cnt = cnt + 1
        script = script + "COMMIT;\n"
        cursor = builder.getCursor()
        cursor.executescript(script)

    def _saveConfigCmds(self, cursor):
        builder = CmdTableBuilder(cursor)
        builder.createTable()
        self._saveConfig(self._cmds, builder)

    def _saveConfigFile(self, cursor):
        builder = FileTableBuilder(cursor)
        builder.createTable()
        self._saveConfig(self._files.values(), builder)

    def _saveConfigJobs(self, cursor):
        builder = JobTableBuilder(cursor)
        builder.createTable()
        self._saveConfig(self._jobs.values(), builder)

    def _saveConfigServices(self, cursor):
        builder = ServiceTableBuilder(cursor)
        builder.createTable()
        self._saveConfig(self._services.values(), builder)

    def _saveConfigServiceSockets(self, cursor):
        builder = ServiceSocketTableBuilder(cursor)
        builder.createTable()
        self._saveConfig(self._serviceSockets.values(), builder)

    def _saveConfigServiceFile(self, cursor):
        builder = ServiceFileTableBuilder(cursor)
        builder.createTable()
        self._saveConfig(self._serviceFiles.values(), builder)

    def saveConfig(self, path):
        conn = sqlite3.connect(path)
        cursor = conn.cursor()
        self._saveConfigFile(cursor)
        self._saveConfigCmds(cursor)
        self._saveConfigJobs(cursor)
        self._saveConfigServices(cursor)
        self._saveConfigServiceSockets(cursor)
        self._saveConfigServiceFile(cursor)
        conn.commit()
        cursor.close()
        conn.close()

    def _is_valid_file(self, file):
        valid_file_ext = [".cfg"]
        if not file.is_file():
            return False
        for ext in valid_file_ext:
            if file.name.endswith(ext):
                return True
        return False

    def _scan_config_file(self, fileName):
        dir = self._path + fileName
        if not os.path.exists(dir):
            return
        with os.scandir(dir) as files:
            for file in files:
                if self._is_valid_file(file):
                    name = file.path[len(self._path) :]
                    self.loadConfig(name)

    def scanConfig(self):
        config_paths = [
            "/system/etc/init",
            "/chip_prod/etc/init",
            "/sys_prod/etc/init",
            "/vendor/etc/init",
        ]
        for fileName in config_paths:
            self._scan_config_file(fileName)

    def getJobPriority(self, jobName):
        jobPriority = {
            "pre-init" : 0,
            "init" : 1,
            "post-init" : 2,
            "early-fs" : 3,
            "fs" : 4,
            "post-fs" : 5,
            "late-fs" : 6,
            "post-fs-data" : 7,
            "firmware_mounts_complete" : 8,
            "early-boot" : 9,
            "boot" : 10
        }

        if (jobPriority.__contains__(jobName)):
            print("getJobPriority %s %d" % (jobName, jobPriority.get(jobName)))
            return jobPriority.get(jobName)
        return 100

    def loadBootEvent_(self, event):
        if self._jobs.__contains__(event.get("name")):
            print("loadBootEvent_ %s %f" % (event.get("name"), event.get("dur")))
            self._jobs.get(event.get("name"))["executionTime"] = event.get("dur")

    def loadBootEventFile(self, bootEventFile):
        if not os.path.exists(bootEventFile):
            print("Error, invalid config file %s" % bootEventFile)
            return
        #print("loadConfig fileName = %s" % fileName)
        with open(bootEventFile, encoding='utf-8') as content:
            try:
                root = json.load(content)
                for item in root:
                    self.loadBootEvent_(item)
            except:
                pass
        pass

    def loaModuleInfo(self, fileNames, moduleName, parrName) :
        for i in range(0, len(fileNames)):
            print("loaModuleInfo fileNames %s " % (fileNames[i]))
            if fileNames[i].endswith(".para.dac") or fileNames[i].endswith(".para") or fileNames[i].endswith(".cfg"):
                if fileNames[i].startswith("/") :
                    fileId = self.addFile(fileNames[i], moduleName, parrName)
                else:
                    fileId = self.addFile("/" + fileNames[i], moduleName, parrName)
                print("loaModuleInfo file %d %s %s" % (fileId, fileNames[i], moduleName))
                pass

    def loadSystemModuleInfoFile(self, dir):
        moduleInfoPath = self._path + dir
        print("Load module info %s " % moduleInfoPath)
        if not os.path.exists(moduleInfoPath):
            print("Error, invalid config file %s" % moduleInfoPath)
            return
        with open(moduleInfoPath, encoding='utf-8') as content:
            try:
                root = json.load(content)
                for item in root:
                    if (not item.__contains__("type")) or item.get("type") != "etc" :
                        continue
                    if item.__contains__("dest") :
                        self.loaModuleInfo(item.get("dest"), item.get("subsystem_name"), item.get("part_name"))
            except:
                import traceback
                traceback.print_exc()

def Collect(options):
    input = os.path.realpath(options.input)
    output = os.path.realpath(options.output)
    if not os.path.exists(output) or not os.path.exists(input):
        print("Failed to collect startup config info input %s output %s" % (input, output))
        return

    parser = ConfigParser(input + "/packages/phone")
    parser.loadSystemModuleInfoFile("/system_module_info.json")
    parser.loadConfig("/system/etc/init.cfg")
    if options.__contains__('bootevent') and options.bootevent :
        parser.loadBootEventFile(os.path.realpath(options.bootevent))

    parser.scanConfig()
    parser.saveConfig(output + "/archinfo.db")
    return parser.getFiles()

if __name__ == '__main__':
    args_parser = _create_arg_parser()
    options = args_parser.parse_args()
    Collect(options)
